算符又稱為運算子,算數、比較、邏輯運算子使用比來跟其它程式語言都差不多,
以下是Operators的分類 :
所謂零值,是指該型別具有預設或是空值(empty value)。
型別 | 零值 |
---|---|
整數型別 | 0 |
浮點數型別 | 0.0 |
字符串型別 | 空字串 ("" 或 "" ) |
布林型別 | false |
陣列型別 | 依元素型別而定 |
切片型別 | nil |
地圖型別 | nil |
通道型別 | nil |
函式型別 | nil |
接口型別 | nil |
指標型別 | nil |
自訂結構型別 | 依欄位型別而定 |
當我們使用 int、string、bool 這類值傳遞給函數時,Go 語言會在函數中複製這些值,建立出新的變數,這意味著你呼叫函數時,若函數對值做出更動,那麼原值也不會受到更動,能減少程式的錯誤。
這種傳值方式叫做值傳遞,在值傳遞中,函數接收的是原始值的一個複本,而不是原始值本身。這意味著在函數內對該值的修改不會影響到原始值。這種方式確保了函數內外的數據獨立性,有助於減少副作用和錯誤。
然而,這種記憶體管理系統,叫做堆疊(stack),每個參數都會在 stack 中獲得自己的記憶體,而越多值在函數之間傳遞,這樣複製動作越多就會消耗越多記憶體。
另一種傳值替代方式就是 Pointer 指標。
指標和值是兩回事,而指標唯一的用途就只是拿來取得值而已,
可以將指標想像成通往值的路標,想要取得值,就需要照的路牌走。
(資料來源:GeekForGeek-Pointers in Golang)
指標變數存的是一個位址,而位址指向的空間才是值。
如圖所示變數x,儲存的值為100,而此變數的記憶體位址在0x0201,然而此時:
var x int = 100
var ptr *int = &x // 使用 & 運算符可以取得變數的記憶體地址,並將其分配給指標變數
取值
fmt.Println(ptr) //輸出結果位址 : 0x0201
fmt.Println(*ptr) // 輸出結果是位址指向的值 : 100
// 可以使用 * 運算符來訪問指標指向的值,這稱為解引用。
關於指標變數賦值取值回在後續說明。
當談到指標(pointers)時,我們需要考慮到它們在 Go 語言中的角色
以及與 "pass by reference" 之間的區別。
指標的角色:
你可以將指標想像成是通向值的路標。就像在地圖上跟著路牌找到特定的地點一樣,透過指標,我們可以找到存儲在某個位置的值,這些指標本身並不儲存實際的數據,它們只是指向某個變數或數據的位置。換句話說,就是存儲記憶體"位置"的變數。
"pass by reference" 和 Go 的指標:
"Pass by reference" 是一種函數參數傳遞方式,其中函數接收的是變數的引用或記憶體位置,而不是變數的複本。當你通過引用對變數進行更改時,原始變數也會受到影響,因為它們實際上指向相同的記憶體位置。
在 Go 語言中,函數的參數傳遞方式是 "pass by value",這意味著函數接收的是變數的複本,而不是原始變數的引用。然而,Go 語言提供了指標的概念,你可以將指標傳遞給函數,並在函數內部通過指標修改原始變數的值。
這裡的關鍵區別在於,Go 語言不是直接 "pass by reference",而是透過指標實現 "pass by value with a pointer"。也就是說,函數接收的是指標的副本,但這個指標副本仍然指向相同的記憶體位置,因此可以影響原始變數。
總結來說
指標在 Go 中扮演著重要的角色,用於間接訪問值。Go 語言使用 "pass by value with a pointer" 的方式處理函數參數傳遞,這意味著函數接收的是指標的副本,但這些副本仍然指向相同的記憶體位置,因此可以修改原始變數的值。這不同於嚴格的 "pass by reference" 概念,其中函數接收的是原始變數的引用。
取得指標有幾種常見的方式,我們可以使用 * 關鍵字來宣告指標變數,也可以使用 new() 函式和 & 運算符來取得指標。
(1) 在型別前面加上 *
var <變數> *<型別>
上述宣告方式初值為nil。
var ptr *int // 宣告一個指向整數型別的指標變數
(2) Go 語言提供了 new() 函式,可以用來取得特定型別的記憶體並返回其指標。new() 函式的工作方式是分配足夠的記憶體來容納該型別的零值,然後返回這個記憶體的指標。以下是一個使用 new() 函式的範例:
<變數> := new(<型別>)
var <變數> = new(<型別>)
var ptr = new(int) // 使用 new() 取得一個整數的指標
這種方式將創建一個指向指定型別的指標,並為其分配記憶體,初值為該型別的零值(對於整數,初值為 0)。
(3) 直接使用 & 運算符來取得現有變數的指標:
<變數 1> := &<變數 2>
例如 :
package main
import "fmt"
func main() {
// 宣告一個整數變數 x
x := 42
// 取得變數 x 的指標,並存儲在 ptr 中
ptr := &x
// 打印變數 x 的值和指標 ptr 的值
fmt.Println("x 的值:", x)
fmt.Println("ptr 的值:", *ptr) // 使用 * 運算符來取得指標指向的值
}
這個程式首先宣告一個整數變數 x,然後使用 & 運算符來取得變數 x 的指標,並將它存儲在變數 ptr 中。最後,使用 * 運算符來取得指標 ptr 指向的值,並將其印出。
這些方式都可以用來取得指標,但它們的用途略有不同:
從指標取得值的過程稱為解引用(Dereferencing),這是指標的一個重要操作。解引用允許我們取得指標指向的記憶體位置上存儲的值,也就是原始變數的值。要解引用一個指標,我們使用 * 運算符,然後後接上指標變數的名稱。
<值> = *<指標變數>
這個操作會取得指標指向的記憶體位置上的值,並將其賦值給一個新的變數。這樣,我們就可以使用該變數來訪問和操作原始變數的值。
需要注意 常見的錯誤是試圖去解除一個無值指標,也就是nil,Go不會幫你檢查,等實際運行時才會報錯,
Dereferencing之前,最好先檢查使否為nil。
import "fmt"
func main() {
var p *int
//試圖解無值指標
if p != nil {
fmt.Println(*p)
} else {
fmt.Println("p 是 nil 啦")
}
}
關於指標用程式範例來了解 :
package main
import "fmt"
func main() {
// 使用 var 關鍵字聲明一個變數 y,並將其初始化為 500
var y = 500
// 使用 var 關鍵字聲明一個指標變數 p,並將其初始化為變數 y 的記憶體地址。
var p = &y
// 輸出 y 變數的值,這是 500。
fmt.Println("輸出 y 變數的值 = ", y)
// 輸出變數 y 的記憶體地址。
fmt.Println("輸出變數 y 的記憶體地址。", &y)
// 輸出指標變數 p 的值,這是變數 y 的記憶體地址。
fmt.Println("輸出指標變數 p 的值,這是變數 y 的記憶體地址 : ", p)
// 使用 * 運算符對指標變數進行解引用,這將返回指標所指向的記憶體位置上存儲的值,即變數 y 的值。
fmt.Println("解引用,這將返回指標所指向的記憶體位置上存儲的值,即變數 y 的值 : ", *p)
// 通過指標 p 修改變數 y 的值,將其設置為 500。
*p = 1000
// 再次輸出 y 變數的值,現在它已經更改為 500。
fmt.Println("再次輸出 y 變數的值,現在它已經更改為 : ", y)
}
輸出結果 :
函式的部分會在後續做介紹。
這邊著重重點在於,如果是以指標形式傳入函式,如果對其值做變動,就真的會改變原始變數,使用這類設計時需要謹慎。
package main
import "fmt"
// 定義一個帶有整數類型指標參數的函式
func ptf(a *int) {
// 解引用指針並賦值
*a = 500
}
func main() {
// 定義一個普通變數 x
var x = 100
fmt.Printf("函式調用前 x 的值為:%d\n", x)
// 定義一個指標變數 pa 並將 x 的地址分配給它
var pa *int = &x
// 通過將指針傳遞給函式來調用函式
ptf(pa)
fmt.Printf("函式調用後 x 的值為:%d\n", x)
}
輸出結果 :
package main
import "fmt"
// 定義一個帶有整數類型指針參數的函式
func ptf(a *int) {
// 解引用指針並賦值
*a = 500
}
func main() {
// 定義一個普通變數 x
var x = 100
fmt.Printf("函式調用前 x 的值為:%d\n", x)
// 通過傳遞變數 x 的地址來調用函式
ptf(&x)
fmt.Printf("函式調用後 x 的值為:%d\n", x)
}
fmt.Printf() 使用一種格式化樣板語言(template language)
官方文件
樣板 | 說明 |
---|---|
%v | 默認格式 |
%+v | 添加字段名 |
%#v | Go 語法表示 |
%T | 顯示值的類型 |
%t | 布林值 |
%d | 十進制整數 |
%b | 二進制表示 |
%o | 八進制表示 |
%x | 十六進制表示(小寫字母) |
%X | 十六進制表示(大寫字母) |
%c | 字符 |
%s | 字符串 |
%q | 雙引號字串 |
%f | 浮點數 |
%e | 科學記號(小寫'e') |
%E | 科學記號(大寫'E') |
%g | 更簡潔的 %e 或 %f |
%G | 更簡潔的 %E 或 %f |
%p | 指針的十六進制表示 |
%U | Unicode 格式:U+1234 |
%v | 值的默認格式 |
%b | 布林值(true 或 false) |
%x | 十六進制表示(小寫字母) |
%X | 十六進制表示(大寫字母) |
%d | 十進制整數 |
%o | 八進制表示 |
%s | 字符串 |
%q | 雙引號字串 |
%v | 默認格式 |
使用fmt.Printdf()必須在字串尾部自己加上換行符號 (\n)
以上就是主要著重於指標在Go語言的基本的整理,如果有觀念或文章上的錯誤,歡迎各位提出~~~ > <。